package metier.modele;

import java.net.Socket;
import java.net.ServerSocket;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.LinkedList;
import java.util.Timer;
import java.util.TimerTask;
import java.awt.Color;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.Runnable;

import org.jdom.JDOMException;

import metier.outils.ExportSvg;
import metier.outils.ImportSVG;

import physique.Communicateur;
import physique.Connexion;



import client.Gestionnaire_Serveur;

/**
 * 
 * @author Quentin, Vincent, Charlie
 *
 */
public class Serveur implements Communicateur, Runnable
{

	private int port;
	
	private int nbClientsMax;
	private int nbClients = 0;
	
	private int frequenceSauvegarde;
	private Timer saveTimer;
	private Timer pingTimer;
	static int PING_INTERVALLLE = 10;
	
	private ServerSocket serv;
	private Connexion cnx;
	
	private LinkedList<Connexion> lConnexion;
	private LinkedList<Figure> lFigure;
	private LinkedList<Utilisateur> lUtilisateur;
	
	private Gestionnaire_Serveur gServeur;
		
	private String workspace = null;

	private ExportSvg exportSvg;
	
	/**
	 * Initialisation du serveur
	 * @param port
	 * @param nbClientMax
	 * @param frequenceSauvegarde
	 * @param gServeur
	 * @param path répertoire des backups
	 * @throws IOException l'erreur est catchée plus haut, dans la classe 'Gestionnaire_Serveur'
	 */
	public Serveur(int port, int nbClientMax, int frequenceSauvegarde, Gestionnaire_Serveur gServeur,String workspace) throws IOException
	{
		this.workspace = workspace;
		this.port = port;
		this.nbClientsMax = nbClientMax;
		this.frequenceSauvegarde = frequenceSauvegarde;
		lFigure = new LinkedList<Figure>();
		lUtilisateur = new LinkedList<Utilisateur>();
		this.gServeur = gServeur;
		creationServeur();
	}

	/**
	 * Création de la socket
	 * @throws IOException erreur catchée dans le constructeur
	 */
	public void creationServeur() throws IOException
	{
		serv = new ServerSocket(port, nbClientsMax);
		Thread t = new Thread(this);
		t.start();
	}

	/**
	 * Déconnexion du serveur. Envoi d'une constante à tous les utilisateurs
	 * connectés pour les prévenirs
	 */
	public void deconnexionServeur()
	{
		// On envoie la MAJ
		for (int i = 0; i < lConnexion.size(); i++)
		{
			((Connexion) lConnexion.get(i)).Envoie(Constantes.SERVEUR_CLOSE);
		}
		stopSaveTimer();
		stopPingTimer();
		// TODO Supprimer chaque user (socket)
		for (int i = 0; i < lConnexion.size(); i++)
		{
			lConnexion.get(i).close();
		}
		try
		{
			serv.close();
		}
		catch (IOException e) 
		{
			System.out.println("Impossible de stopper le serveur : " + e);
		}
		
		removePIDFile();
		
	}

	
	/**
	 * Exécution du Thread
	 */
	public void run()
	{
		lConnexion = new LinkedList<Connexion>();
		startSaveTimer();
//		startPingTimer();

		try {

			while (true)
			{
				System.out.println("En attente d'un client ...");
				
				Socket cli = serv.accept();
	
				cnx = new Connexion(cli, this);
			
				nbClients++;
				if (nbClients > nbClientsMax)
				{
					cnx.Envoie(Constantes.TOO_MANY_CONNECTED);
					cnx.close();
				}
				else
				{
					System.out.println("Client connecté ...\n");
					// Si un client se connecte en retard, il doit récupérer le
					// Travail déjà fait, nous lui envoyons donc les figures.
					for (int i = 0; i < lFigure.size(); i++)
					{
						cnx.Envoie(lFigure.get(i));
					}
	
					// Puis la liste des utilisateurs connectés
					for (int i = 0; i < lUtilisateur.size(); i++)
					{
						cnx.Envoie(lUtilisateur.get(i));
					}
	
					// Puis on ajoute le client {Ou plutôt sa connection} à la liste.
					lConnexion.add(cnx);
	
					// MAJ du nombre de connectés
					gServeur.updateConnectes(lConnexion.size());
				}
			}

		} catch (Exception e) 
		{
			System.out.println("Fermeture du serveur : " + e);
		}
	}

	
	/**
	 * Traite tous les messages reçus via la socket
	 */
	public synchronized void traiteMessage(Object O) {
		try {

			int status;
			
			if (O instanceof Utilisateur)   ///////////////////////// UTILISATEUR
			{
				Utilisateur u = null;
				u = (Utilisateur) O;
				status = u.getStatus();
				if (status == Utilisateur.AJOUT)
				{
					System.out.println("SERVEUR: Réception utilisateur AJOUT (" + u.toString() + ")");
					lUtilisateur.addLast(u);
					displayList(lUtilisateur);
					gServeur.updateConnectes(lUtilisateur.size());
				}
					
				else if (status == Utilisateur.SUPPRESSION)
				{
					System.out.println("SERVEUR: Réception utilisateur SUPPRESSION (" + u.toString() + ")");
					// On le retire
					lUtilisateur.remove(u.getId());
					lConnexion.get(u.getId()).close();
					lConnexion.remove(u.getId());
					displayList(lUtilisateur);
					gServeur.updateConnectes(lUtilisateur.size());
					// On envoie la MAJ
					for (int i = 0; i < lConnexion.size(); i++)
					{
						((Connexion) lConnexion.get(i)).Envoie(O);
					}
					Thread.sleep(10);
					for (int i = 0; i < lConnexion.size(); i++)
					{
						((Connexion) lConnexion.get(i)).Envoie(i);
					}
				}
				
				else if (status == Utilisateur.INIT)
				{
					System.out.println("SERVEUR: Réception utilisateur INIT (" + u.toString() + ")");
					String nom = u.getNom();
					while (userAlreadyExists(nom) == true)
					{
						nom += "_";
					}
					Color color = Constantes.COLORS[lUtilisateur.size() % Constantes.COLORS.length];
					Utilisateur userReturned = new Utilisateur (nom, lUtilisateur.size(), Utilisateur.INIT, color);
					for (int i = 0; i < lConnexion.size(); i++)
					{
						((Connexion) lConnexion.get(i)).Envoie(userReturned);
					}
					userReturned.setStatus(Utilisateur.AJOUT);
					lUtilisateur.add(userReturned);
					displayList(lUtilisateur);
					gServeur.updateConnectes(lUtilisateur.size());
				}
				
				gServeur.updateConnectes(lUtilisateur.size());
				
				
			}
			
			else if (O instanceof Figure)         ///////////////////////// USER
			{
				Figure f = (Figure) O;
				status = f.getStatus();
				if (status == Figure.AJOUT) // On ajoute
				{
					System.out.println("SERVEUR: Réception figure AJOUT");
//					Tools_Rect rect = new Tools_Rect();
//					lFigure.addLast(rect);
//					lFigure.remove(rect);
					lFigure.addLast(f);
				}
					
				else if (status == Figure.SUPPRESSION)
				{
					System.out.println("SERVEUR: Réception figure SUPPRESSION");
					lFigure.remove(f.getId());
				}
				
				else if (status == Figure.EDIT)
				{
					System.out.println("SERVEUR: Réception figure EDIT");
					lFigure.remove(f.getId());
					lFigure.addLast(f);
				}
				
				// On envoie aux clients les changements
				for (int i = 0; i < lConnexion.size(); i++)
				{
					((Connexion) lConnexion.get(i)).Envoie(f);
				}
				
				if (status == Figure.EDIT) // On remet le status de base 'AJOUT'
					lFigure.get(lFigure.size()-1).setStatus(Figure.AJOUT);
				
				// On re-attribue un ID a chaque figure
				for (int i = 0; i < lFigure.size(); i++)
				{
					lFigure.get(i).setId(i);
				}
				
			}
					
			else if(O instanceof Integer){
				
				Integer val = (Integer)O;
				if(val == Constantes.INIT_DRAW){
					lFigure.clear();
					for (int i = 0; i < lConnexion.size(); i++)
					{
						((Connexion) lConnexion.get(i)).Envoie(Constantes.INIT_DRAW);
					}
				}
				else if (val == Constantes.PONG) {
					System.out.println("SERVEUR: PONG!");
				}
				
			}
			else 
			{
				System.out.println("SERVEUR: Objet reçu non identifié : " + O.toString());
			}

		} catch (Exception e)
		{
			e.printStackTrace();
		}
	}
	
	
	/**
	 * Méthode appelée lors de l'exportation des figures dans un fichier SVG
	 * @throws IOException 
	 * @throws FileNotFoundException 
	 */
	public void save() throws FileNotFoundException, IOException
	{
		if(this.lFigure.size() != 0)
		{
			System.out.println("Save des svg dans le dossier " + this.getDirSavSvg());
			String path = this.getDirSavSvg();
			Date d = new Date();
			SimpleDateFormat sdf = new SimpleDateFormat("y-MM-d_hh-mm-ss");
			path += File.separatorChar;
			path += sdf.format(d);
			path+= ".svg";
			System.out.println("Save dessin to svg : "+path );
			exportSvg = new ExportSvg(path,lFigure);
			exportSvg.graphicToSVG();
		}
		
	}
	
	
	/**
	 * Timer pour l'envoi du PING (start)
	 */
	public void startPingTimer()
	{
			pingTimer = new Timer();
			pingTimer.schedule(new TimerTask()
			{
				public void run()
				{
					sendPingToAll();
				}
			}, PING_INTERVALLLE * 1000, PING_INTERVALLLE * 1000);
			
		
	}
	public void sendPingToAll()
	{
		for (int i = 0; i < lConnexion.size(); i++)
		{
			((Connexion) lConnexion.get(i)).Envoie(Constantes.PING);
		}
	}
	
	/**
	 * Timer pour l'envoi du PING (stop)
	 */
	public void stopPingTimer()
	{
		if (pingTimer == null) return;
		try
		{
			pingTimer.cancel();
			pingTimer = null;
		}catch(NullPointerException nl)
		{
		}
	}
	
	
	/**
	 * Timer pour le backup (start)
	 */
	public void startSaveTimer()
	{
			saveTimer = new Timer();
			saveTimer.schedule(new TimerTask()
			{
				public void run()
				{
					try {
						save();
					} catch (FileNotFoundException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					} catch (IOException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			}, frequenceSauvegarde * 60 * 1000, frequenceSauvegarde * 60 * 1000);
			
		
	}
	
	/**
	 * Timer pour le backup (stop)
	 */
	public void stopSaveTimer()
	{
		if (saveTimer == null) return;
		try
		{
			saveTimer.cancel();
			saveTimer = null;
		}catch(NullPointerException nl)
		{
		}
	}
	
	/**
	 * Est-ce qu'un pseudo est déjà présent dans la liste des connectés ?
	 * @param nom
	 * @return true si déjà présent, false sinon
	 */
	public boolean userAlreadyExists(String nom)
	{
		for (Utilisateur user : lUtilisateur)
		{
			if (user.getNom().equalsIgnoreCase(nom))
				return true;
		}
		return false;
	}
	
	
	public boolean checkPIDFile() {
		if (workspace == null) return false;
		String pid = workspace + File.separatorChar + Constantes.SERVEUR_PID;
		File file = new File(pid);
		if (file.exists() == true)
			return true;
		return false;
	}

	public void removePIDFile() {
		if (workspace == null) return;
		String pid = workspace + File.separatorChar + Constantes.SERVEUR_PID;
		File file = new File(pid);
		if (file.exists() == true)
			file.delete();
	}

	public void loadLastSVGFile() throws JDOMException {
		if (workspace == null) return;
		File svgFolder = new File(workspace);
		if (svgFolder.isDirectory())
		{
			File[] svgFiles = svgFolder.listFiles(new SVGFileFilter());
			if (svgFiles.length > 0)
			{
				Arrays.sort(svgFiles, new Comparator<File>() {
					public int compare (File f1, File f2) {
						return Long.valueOf(f2.lastModified()).compareTo(f1.lastModified());
					}
				});
//				for (File file : svgFiles) {
//					System.out.println(file.lastModified()+" "+file.getName());
//				}
				System.out.println("Loading from SVG File : " + svgFiles[0].getPath());
				ImportSVG isvg = new ImportSVG(svgFiles[0], lFigure);
				isvg.SVGToGraphic();
				System.out.println("loadLastSVGFile: " + lFigure.size() + " figure(s) chargée(s)");
				
			}
		}
		
	}

	public void addPIDFile() {
		if (workspace == null) return;
		String pid = workspace + File.separatorChar + Constantes.SERVEUR_PID;
		File file = new File(pid);
		if (file.exists())
			removePIDFile();
		FileWriter fichier = null;
		try {
			fichier = new FileWriter(file, true);
			try {
				   fichier.write("1");
				   fichier.close();
				} catch(IOException ie) {
				   ie.printStackTrace();
				}
		} catch (IOException e) {
			e.printStackTrace();
		}

	}
	

	/**
	 * Affiche une liste
	 * @param l liste à afficher
	 */
	public void displayList(LinkedList<?> l)
    {
    	for (Object object : l) {
			System.out.print(object.toString() + " | ");
		}
    	System.out.println();
    }

	public Timer getFreqSaveTimer() {
		return saveTimer;
	}

	public void setFreqSaveTimer(Timer freqSaveTimer) {
		this.saveTimer = freqSaveTimer;
	}
	public String getDirSavSvg() {
		return workspace;
	}

	public void setDirSavSvg(String dirSavSvg) {
		this.workspace = dirSavSvg;
	}
	
}